/***************************************************************************
 * CLASS:		MiscIconWell
 * Copyright (C) 1995 Robert Todd Thomas
 * Use is governed by the MiscKit license
 *
 *	See the header file for more information.
 *
 * This object is included in the MiscKit by permission from the author
 * and its use is governed by the MiscKit license, found in the file
 * "LICENSE.rtf" in the MiscKit distribution.  Please refer to that file
 * for a list of all applicable permissions and restrictions.
 ***************************************************************************/

#import <misckit/MiscString.h>		// here for backward compatibility
#import "MiscIconWell.h"

/*
 * A proper implementation of versioning. If you add ivars to the class (or
 * just want to dork with the reading/writing) check out the comments tagged
 * "Archiving: READ ME". BJM 5/24/94
 */
 
/*
 * **** Archiving: READ ME **** This is the current and defined version of the
 * class. It is used to identify what data will be written and how that will
 * happen. If the read/write stuff is modified AT ALL, this must be bumped up
 * (I always bump by one) and the other comments must be followed. Failure to
 * do so will result in palettes and nibs that cannot be read. BJM 5/24/94
 */
#define MISC_ICON_WELL_VERSION 1
 
@implementation MiscIconWell
 
+ initialize
{
    if (self == [MiscIconWell class]) 
	{
		/*
		 * **** Archiving: READ ME **** After bumping the _VERSION, it is
		 * considered common practice to add a comment line indicating the new
		 * version number, date, and modifier. Optionally, the reason for the
		 * change. There is no need to modify the setVersion message. BJM
		 * 5/24/94 
		 */
		// version 0: initial.  (bjm)
   		[[MiscIconWell class] setVersion:MISC_ICON_WELL_VERSION];
    }
    
    return self;
}



- initFrame: (const NXRect *)frameRect
{
  	[super initFrame: frameRect];

	filename = NULL;	
	[self setAllowDoubleClickLaunch: YES];
	return self;
}



- initDragTypes
{	
	// Register to accept drags that are on the NXFilenamePboard.
  	const char *const types[] = {NXFilenamePboardType};
  
	[self registerForDraggedTypes: (const char *const *)&types count: 1];
	return self;
}


	
- free
{
	if (filename)
		NX_FREE (filename);

	return [super free];
}



- (const char *)filename
{   
	// Returns the filename that the view currently represents. If the view is
	// empty, NULL is returned.
	return (const char *)filename;
}



- setImage: (NXImage *)anImage
{
	// Overidden from MiscDragView since you could potentially set anImage
	// to nil which would erase what was contained in the view. Therefore
	// we should check for that and free the filename if there is no image.

	[super setImage: anImage];
	
	if ([self image] == nil)
		if (filename && (strlen(filename)))
		{
			NX_FREE (filename);
			filename = NULL;
		 }
	return self;
}



- setImageByFilename: (const char *)aFilename
{
	// Overridden from MiscDragView so the icon representation of the file
	// is fetched instead of the image itself (likely the filename is not
	// an image). It also has a small hack that if there are more than one
	// filename that it steals that multiple.tiff from Librarian.app. 

	// Passing aFilename as NULL essentially erases the image in the view.
	if (aFilename == NULL)
		return [super setImageByFilename: NULL];
	
	if (filename && strlen(filename))
		NX_FREE (filename);
	 
	filename = NX_MALLOC (filename, char, strlen(aFilename)+1);
	strcpy (filename, aFilename);
	
	/*
	 * Multiple filenames are separated by tabs. We can't use -numWords because
	 * numWords checks for any NXIsSpace which includes spaces. Result: if we
	 * use numWords, it treats any filename with spaces as "multiple". BJM
	 * 5/24/94    
	 */
	if (index([self filename], '\t') != NULL)
		[super setImageByFilename: "/NextApps/Librarian.app/multiple.tiff"];
	else	
		[self setImage: [ [Application workspace] getIconForFile: aFilename] ];
	
	return self;
}



- setAllowDoubleClickLaunch: (BOOL)aBool
{
	allowDoubleClickLaunch = aBool;
	return self;
}



- (BOOL)allowDoubleClickLaunch
{
	return allowDoubleClickLaunch;
}



- launch:sender
{
	// Launch/open file in WS: BJM 5/24/94
	// Did not work for launching multiple files under 3.0 (and possilbly 3.x)
	// and was fixed by Stephen Fitzpatrick 07/17/94.

	NXPoint   p;
	
	// p is supposed to be the location of the image:
	// I'm not sure what value it should be set to.
	p = bounds.origin;
	
	if (index([self filename], '\t'))
	{
		// Multiple files.
		// Make a copy of the string, and iterate through each filename, 		
		// replacing tabs with NULLs.
		
		char	*start, *end;
		int	length = strlen ([self filename]);
		char	copy[length+1];
		
		strcpy(copy, [self filename]);
		start = copy;
	
		// Open the first file with animation.
		end = index(start, '\t');
		*end = '\0';
		
		// causes the animation of the icon
		[[Application workspace]
				openFile:start
				fromImage:[self image] at:&p inView:self
		];
		start = end+1;
		
		// Now do the rest of the files without animation.
		do
		{
			end = index(start, '\t');
			if (end)
			{
				*end = '\0';
				[[Application workspace] openFile:start];
				start = end+1;
			}
			else // The last file.
				[[Application workspace] openFile:start];
		}
		while (end);
	}
	else
	{
		// There's only one file: open it with animation.
		[[Application workspace]
				openFile: [self filename]
				fromImage:[self image] at:&p inView:self
		];
	}
	
	return self;
}



- mouseDown: (NXEvent *)theEvent
{
	// Override mouseDown to check if the icon was double clicked. If so, then
	// launch it from workspace, else let super handle it.

	/*
	 * Only attempt to open file if there (1) DC is allowed, (2) this is a DC
	 * event, and (3) there is a file to open. BJM 5/24/94
	 */
	if ([self allowDoubleClickLaunch] && theEvent->data.mouse.click == 2)
		if (filename != NULL && strlen(filename) > 0)
			return [self launch:self];
		
	[super mouseDown: theEvent];
	return self;
}



- calculateDragPoint: (NXPoint *)dragPoint andOffset: (NXPoint *)offset
{
	// Make the dragPoint be the middle of the image, so it looks nice. This
	// is overridden from MiscDragView.

	dragPoint->x -= imageSize.width/2.0;
    dragPoint->y -= imageSize.width/2.0;
	return self;
}


	
- (BOOL)setupForSourceDrag
{
	// Put the data on the pasteboard when a source drag takes place, and
	// also choose the image to drag. 
	// ...only if there is something to drag... BJM 5/24/94

	if (filename != NULL && strlen(filename) > 0) 
	{
	  	id  dragPB = [self draggingPasteboard];
	  
		[dragPB declareTypes:&NXFilenamePboardType num:1 owner:self];		
		[dragPB writeType: NXFilenamePboard 
			data: [self filename] 
			length: strlen([self filename]) ];
			
		[self setDragImage: theImage];
		return YES;
	}
	return NO;
}



- (BOOL)performDragOperation: sender
{
	// Check if an incoming dragged icon is using the NXFilenamePboardType. If
	// not, then don't accept the drag.

	id  dragPB = [self draggingPasteboard];
  	char  *pbData;
  	int  pbLength;

	if ([dragPB readType: NXFilenamePboardType data: &pbData 
			length: &pbLength] == nil)
	{
		// If you are ending the drag, make sure to clean up.
		[self cleanupAfterDestinationDrag];
		return NO;	
	 }
	 
	return YES;
}



- concludeDragOperation: sender
{
	// Take the data from the pasteboard and set the new image.

  	id  dragPB = [self draggingPasteboard];
  	char  *pbData;
  	int  pbLength;
  
	[dragPB readType: NXFilenamePboardType data: &pbData 
			length: &pbLength];
	[self setImageByFilename: pbData];
	[dragPB deallocatePasteboardData: pbData length: pbLength];

	// Make sure to call super's implementation.
	[super concludeDragOperation: sender];
    return self;
}



/*********** Archiving *************/

- read:(NXTypedStream *)stream
{
    int version;
	id  oldfilename;

    [super read:stream];
    
	version = NXTypedStreamClassVersion(stream, "MiscIconWell");
        
	/*
	 * **** Archiving: READ ME **** This code (and its analogue in write:) is
	 * critical. When you bump MISC_ICON_WELL_VERSION, copy the whole _current_
	 * case ("case MISC_ICON_WELL_VERSION: ... break;"), and duplicate it.
	 * Change the "MISC_ICON_WELL_VERSION" in the old case to the OLD
	 * (pre-bump) version number. Now, dork the "new current" version into
	 * whatever you want. See how this code can now read EITHER version out of
	 * the typed stream?
	 *
	 * If you are adding yet another version, leave the previous versionS in
	 * place. That way you can still read archived objects that are more than
	 * one version old. Don't forget to take whatever actions are necessary to
	 * harmonize those old values. For example, if you converted an "int" ivar
	 * to "double", you'd need to (in the old version) to read the int version
	 * into a temp variable, and convert it in to the double var. BJM 5/24/94
	 */
    switch (version)
    {
	// filename used to be a string object
    case 0:
		oldfilename = NXReadObject (stream);
		if (oldfilename)
		{
			NX_MALLOC (filename, char, [oldfilename length]+1);
			strcpy (filename, [oldfilename stringValue]);
			
			[oldfilename free];
		 }
		 	
		NXReadTypes (stream, "c", &allowDoubleClickLaunch);
		break;

    case MISC_ICON_WELL_VERSION:
		NXReadTypes (stream, "*c", 
						&filename, &allowDoubleClickLaunch);
		break;

    default:
		NXLogError("[%s %s] - unknown version of %s in typed stream",
			[[self class] name], sel_getName(_cmd), [[self class] name]);
		break;
    }

    return self;
}

- write:(NXTypedStream *)stream
{
  id  oldfilename = nil;
  
    [super write:stream];
    
	/*
	 * **** Archiving: READ ME **** Home stretch. Now (just like read:)
	 * duplicate the current case ("case MISC_ICON_WELL_VERSION: ... break;").
	 * Once again, change the "MISC_ICON_WELL_VERSION" of the first one to the
	 * OLD version number. Now adjust the new/current MISC_ICON_WELL_VERSION
	 * (remember, you've bumped it) to the new way of writing vars. See how
	 * this code (because the constant is in the switch statement) always
	 * writes out ONLY the current version, but leaves the old version(s)
	 * around to posterity. DO NOT DELETE THE OLD VERSIONS. Leave them to
	 * clutter up the world. BJM 5/24/94  
	 */
    switch (MISC_ICON_WELL_VERSION)
    {
    case 0:
		NXWriteObject(stream, oldfilename);
		NXWriteTypes (stream, "c", &allowDoubleClickLaunch);
		break;

    case MISC_ICON_WELL_VERSION:
		NXWriteTypes (stream, "*c", &filename, &allowDoubleClickLaunch);
		break;

    default:
		NXLogError("[%s %s] - unknown version of %s in typed stream",
			[[self class] name], sel_getName(_cmd), [[self class] name]);
		break;
    }

    return self;
}

/*
 * Methods to support a stringValue - basically our filename, handled
 * just like stringValue in Control objects.  (This change didn't involve
 * adding any ivars or any dorking with reading/writing, so for now I've
 * left the class version number alone.)
 * shayman
 * Aug 2 1994
 */
- (const char *)stringValue
{
    return [self filename];
}
- setStringValue:(const char *)aValue
{
    return [self setImageByFilename:aValue];
}
- takeStringValueFrom:sender
{
    if ( [sender respondsTo:@selector(stringValue)] )
	return [self setStringValue: [sender stringValue]];
    return nil;
}

@end


/***************************************************************************
  CHANGES:
  
  Small bug fixes: BJM 5/24/94 (Bruce McKenzie, spuds@netcom.com)
    1) changed "- (char *)filename" to "- (const char *)filename"
    2) fixed recognition of multiple files in filename to look for tab,
        rather than "any whitespace".
    3) fixed places where it was assumed that filename ivar was ever nil
    4) Added proper archiving/versioning to class.
    5) Added "- launch:sender" method
  Small fix: 07/18/94(Stephen Fitzpatrick, sfitzp@elegabalus.cs.qub.ac.uk)
 	6) launch: sender did not launch multiple files (under 3.0), but does now
  Todd Thomas (todd@avocado.supernet.ab.ca) on September 20, 1994
  	7) Removed the MiscString instance so it would have less dependencies
		when palettized.
	8) Overrode setImage from MiscDragView since it may be nil and the
		current filename should be freed and set to NULL.
  General small bug fixes (v1.4) (Todd)
  	9) With the changes to the MiscDragView superclass, it is now
		necessary to call [self cleanupAfterDestinationDrag] if you
		stop the drag by returning NO from either prepareForDragOperation:
		or performDragOperation:.
			 
****************************************************************************/
